package com.example.test;

import org.openhitls.crypto.SM4;
import org.junit.Test;
import static org.junit.Assert.*;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;
import java.security.Security;
import org.openhitls.crypto.jce.HiTls4jProvider;

public class SM4Test {
    private static final byte[] TEST_KEY = new byte[] {
        (byte)0x01, (byte)0x02, (byte)0x03, (byte)0x04, (byte)0x05, (byte)0x06, (byte)0x07, (byte)0x08,
        (byte)0x09, (byte)0x0a, (byte)0x0b, (byte)0x0c, (byte)0x0d, (byte)0x0e, (byte)0x0f, (byte)0x10
    };

    private static final byte[] TEST_KEY_XTS = new byte[] {
        // First 16-byte key
        (byte)0x01, (byte)0x02, (byte)0x03, (byte)0x04, (byte)0x05, (byte)0x06, (byte)0x07, (byte)0x08,
        (byte)0x09, (byte)0x0a, (byte)0x0b, (byte)0x0c, (byte)0x0d, (byte)0x0e, (byte)0x0f, (byte)0x10,
        // Second 16-byte key
        (byte)0x11, (byte)0x12, (byte)0x13, (byte)0x14, (byte)0x15, (byte)0x16, (byte)0x17, (byte)0x18,
        (byte)0x19, (byte)0x1a, (byte)0x1b, (byte)0x1c, (byte)0x1d, (byte)0x1e, (byte)0x1f, (byte)0x20
    };

    private static final byte[] TEST_IV = new byte[] {
        (byte)0x11, (byte)0x22, (byte)0x33, (byte)0x44, (byte)0x55, (byte)0x66, (byte)0x77, (byte)0x88,
        (byte)0x99, (byte)0xaa, (byte)0xbb, (byte)0xcc, (byte)0xdd, (byte)0xee, (byte)0xff, (byte)0x00
    };

    private static final byte[] TEST_DATA = new byte[] {
        (byte)0x01, (byte)0x02, (byte)0x03, (byte)0x04, (byte)0x05, (byte)0x06, (byte)0x07, (byte)0x08,
        (byte)0x09, (byte)0x0a, (byte)0x0b, (byte)0x0c, (byte)0x0d, (byte)0x0e, (byte)0x0f, (byte)0x10,
        (byte)0x11, (byte)0x12, (byte)0x13, (byte)0x14, (byte)0x15, (byte)0x16, (byte)0x17, (byte)0x18,
        (byte)0x19, (byte)0x1a, (byte)0x1b, (byte)0x1c, (byte)0x1d, (byte)0x1e, (byte)0x1f, (byte)0x20
    };

    // Test data that's not block aligned (20 bytes)
    private static final byte[] UNALIGNED_DATA = new byte[] {
        (byte)0x01, (byte)0x02, (byte)0x03, (byte)0x04, (byte)0x05, (byte)0x06, (byte)0x07, (byte)0x08,
        (byte)0x09, (byte)0x0a, (byte)0x0b, (byte)0x0c, (byte)0x0d, (byte)0x0e, (byte)0x0f, (byte)0x10,
        (byte)0x11, (byte)0x12, (byte)0x13, (byte)0x14
    };

    @Test
    public void testSm4CbcPkcs7Padding() {
        SM4 encryptor = new SM4(SM4.SM4_CBC, TEST_KEY, TEST_IV, SM4.MODE_ENCRYPT, SM4.PADDING_PKCS7);
        SM4 decryptor = new SM4(SM4.SM4_CBC, TEST_KEY, TEST_IV, SM4.MODE_DECRYPT, SM4.PADDING_PKCS7);

        // Encrypt
        byte[] encrypted = encryptor.encryptUpdate(UNALIGNED_DATA, 0, UNALIGNED_DATA.length);
        byte[] finalEncBlock = encryptor.encryptFinal();
        byte[] fullEncrypted = combineArrays(encrypted, finalEncBlock);

        // Decrypt
        byte[] decrypted = decryptor.decryptUpdate(fullEncrypted, 0, fullEncrypted.length);
        byte[] finalDecBlock = decryptor.decryptFinal();
        byte[] fullDecrypted = combineArrays(decrypted, finalDecBlock);

        assertArrayEquals("Decrypted data should match original data", UNALIGNED_DATA, fullDecrypted);
        
        // Test with unaligned data
        encryptor = new SM4(SM4.SM4_CBC, TEST_KEY, TEST_IV, SM4.MODE_ENCRYPT, SM4.PADDING_PKCS7);
        decryptor = new SM4(SM4.SM4_CBC, TEST_KEY, TEST_IV, SM4.MODE_DECRYPT, SM4.PADDING_PKCS7);
        
        encrypted = encryptor.encryptUpdate(UNALIGNED_DATA, 0, UNALIGNED_DATA.length);
        finalEncBlock = encryptor.encryptFinal();
        fullEncrypted = combineArrays(encrypted, finalEncBlock);
        
        decrypted = decryptor.decryptUpdate(fullEncrypted, 0, fullEncrypted.length);
        finalDecBlock = decryptor.decryptFinal();
        fullDecrypted = combineArrays(decrypted, finalDecBlock);
        
        assertArrayEquals("Decrypted unaligned data should match original", UNALIGNED_DATA, fullDecrypted);
    }

    @Test
    public void testSm4CbcPkcs5Padding() {
        SM4 encryptor = new SM4(SM4.SM4_CBC, TEST_KEY, TEST_IV, SM4.MODE_ENCRYPT, SM4.PADDING_PKCS5);
        SM4 decryptor = new SM4(SM4.SM4_CBC, TEST_KEY, TEST_IV, SM4.MODE_DECRYPT, SM4.PADDING_PKCS5);

        // Encrypt
        byte[] encrypted = encryptor.encryptUpdate(UNALIGNED_DATA, 0, UNALIGNED_DATA.length);
        byte[] finalEncBlock = encryptor.encryptFinal();
        byte[] fullEncrypted = combineArrays(encrypted, finalEncBlock);

        // Decrypt
        byte[] decrypted = decryptor.decryptUpdate(fullEncrypted, 0, fullEncrypted.length);
        byte[] finalDecBlock = decryptor.decryptFinal();
        byte[] fullDecrypted = combineArrays(decrypted, finalDecBlock);

        assertArrayEquals("Decrypted data should match original data", UNALIGNED_DATA, fullDecrypted);
        
        // Test with unaligned data
        encryptor = new SM4(SM4.SM4_CBC, TEST_KEY, TEST_IV, SM4.MODE_ENCRYPT, SM4.PADDING_PKCS5);
        decryptor = new SM4(SM4.SM4_CBC, TEST_KEY, TEST_IV, SM4.MODE_DECRYPT, SM4.PADDING_PKCS5);
        
        encrypted = encryptor.encryptUpdate(UNALIGNED_DATA, 0, UNALIGNED_DATA.length);
        finalEncBlock = encryptor.encryptFinal();
        fullEncrypted = combineArrays(encrypted, finalEncBlock);
        
        decrypted = decryptor.decryptUpdate(fullEncrypted, 0, fullEncrypted.length);
        finalDecBlock = decryptor.decryptFinal();
        fullDecrypted = combineArrays(decrypted, finalDecBlock);
        
        assertArrayEquals("Decrypted unaligned data should match original", UNALIGNED_DATA, fullDecrypted);
    }

    @Test
    public void testSm4CbcZerosPadding() {
        SM4 encryptor = new SM4(SM4.SM4_CBC, TEST_KEY, TEST_IV, SM4.MODE_ENCRYPT, SM4.PADDING_ZEROS);
        SM4 decryptor = new SM4(SM4.SM4_CBC, TEST_KEY, TEST_IV, SM4.MODE_DECRYPT, SM4.PADDING_ZEROS);

        // Encrypt
        byte[] encrypted = encryptor.encryptUpdate(UNALIGNED_DATA, 0, UNALIGNED_DATA.length);
        byte[] finalEncBlock = encryptor.encryptFinal();
        byte[] fullEncrypted = combineArrays(encrypted, finalEncBlock);

        // Decrypt
        byte[] decrypted = decryptor.decryptUpdate(fullEncrypted, 0, fullEncrypted.length);
        byte[] finalDecBlock = decryptor.decryptFinal();
        byte[] fullDecrypted = combineArrays(decrypted, finalDecBlock);

        // For zeros padding, we need to trim trailing zeros
        int actualLength = UNALIGNED_DATA.length;
        byte[] trimmedDecrypted = Arrays.copyOf(fullDecrypted, actualLength);
        assertArrayEquals("Decrypted data should match original data", UNALIGNED_DATA, trimmedDecrypted);
    }

    @Test
    public void testSm4CbcIso7816Padding() {
        SM4 encryptor = new SM4(SM4.SM4_CBC, TEST_KEY, TEST_IV, SM4.MODE_ENCRYPT, SM4.PADDING_ISO7816);
        SM4 decryptor = new SM4(SM4.SM4_CBC, TEST_KEY, TEST_IV, SM4.MODE_DECRYPT, SM4.PADDING_ISO7816);

        // Encrypt
        byte[] encrypted = encryptor.encryptUpdate(UNALIGNED_DATA, 0, UNALIGNED_DATA.length);
        byte[] finalEncBlock = encryptor.encryptFinal();
        byte[] fullEncrypted = combineArrays(encrypted, finalEncBlock);

        // Decrypt
        byte[] decrypted = decryptor.decryptUpdate(fullEncrypted, 0, fullEncrypted.length);
        byte[] finalDecBlock = decryptor.decryptFinal();
        byte[] fullDecrypted = combineArrays(decrypted, finalDecBlock);

        assertArrayEquals("Decrypted data should match original data", UNALIGNED_DATA, fullDecrypted);
    }

    @Test
    public void testSm4Ctr() {
        // CTR mode doesn't need padding as it's a stream cipher
        SM4 encryptor = new SM4(SM4.SM4_CTR, TEST_KEY, TEST_IV, SM4.MODE_ENCRYPT, SM4.PADDING_NONE);
        SM4 decryptor = new SM4(SM4.SM4_CTR, TEST_KEY, TEST_IV, SM4.MODE_DECRYPT, SM4.PADDING_NONE);

        // Encrypt
        byte[] encrypted = encryptor.encryptUpdate(TEST_DATA, 0, TEST_DATA.length);
        byte[] finalEncBlock = encryptor.encryptFinal();
        byte[] fullEncrypted = combineArrays(encrypted, finalEncBlock);

        // Decrypt
        byte[] decrypted = decryptor.decryptUpdate(fullEncrypted, 0, fullEncrypted.length);
        byte[] finalDecBlock = decryptor.decryptFinal();
        byte[] fullDecrypted = combineArrays(decrypted, finalDecBlock);

        assertArrayEquals("Decrypted data should match original data", TEST_DATA, fullDecrypted);
        
        // Test with unaligned data
        encryptor = new SM4(SM4.SM4_CTR, TEST_KEY, TEST_IV, SM4.MODE_ENCRYPT, SM4.PADDING_NONE);
        decryptor = new SM4(SM4.SM4_CTR, TEST_KEY, TEST_IV, SM4.MODE_DECRYPT, SM4.PADDING_NONE);
        
        encrypted = encryptor.encryptUpdate(UNALIGNED_DATA, 0, UNALIGNED_DATA.length);
        finalEncBlock = encryptor.encryptFinal();
        fullEncrypted = combineArrays(encrypted, finalEncBlock);
        
        decrypted = decryptor.decryptUpdate(fullEncrypted, 0, fullEncrypted.length);
        finalDecBlock = decryptor.decryptFinal();
        fullDecrypted = combineArrays(decrypted, finalDecBlock);
        
        assertArrayEquals("Decrypted unaligned data should match original", UNALIGNED_DATA, fullDecrypted);
    }

    @Test
    public void testSm4Cfb() {
        // CFB mode doesn't need padding as it's a stream cipher
        SM4 encryptor = new SM4(SM4.SM4_CFB, TEST_KEY, TEST_IV, SM4.MODE_ENCRYPT, SM4.PADDING_NONE);
        SM4 decryptor = new SM4(SM4.SM4_CFB, TEST_KEY, TEST_IV, SM4.MODE_DECRYPT, SM4.PADDING_NONE);

        // Encrypt
        byte[] encrypted = encryptor.encryptUpdate(TEST_DATA, 0, TEST_DATA.length);
        byte[] finalEncBlock = encryptor.encryptFinal();
        byte[] fullEncrypted = combineArrays(encrypted, finalEncBlock);

        // Decrypt
        byte[] decrypted = decryptor.decryptUpdate(fullEncrypted, 0, fullEncrypted.length);
        byte[] finalDecBlock = decryptor.decryptFinal();
        byte[] fullDecrypted = combineArrays(decrypted, finalDecBlock);

        assertArrayEquals("Decrypted data should match original data", TEST_DATA, fullDecrypted);
        
        // Test with unaligned data
        encryptor = new SM4(SM4.SM4_CFB, TEST_KEY, TEST_IV, SM4.MODE_ENCRYPT, SM4.PADDING_NONE);
        decryptor = new SM4(SM4.SM4_CFB, TEST_KEY, TEST_IV, SM4.MODE_DECRYPT, SM4.PADDING_NONE);
        
        encrypted = encryptor.encryptUpdate(UNALIGNED_DATA, 0, UNALIGNED_DATA.length);
        finalEncBlock = encryptor.encryptFinal();
        fullEncrypted = combineArrays(encrypted, finalEncBlock);
        
        decrypted = decryptor.decryptUpdate(fullEncrypted, 0, fullEncrypted.length);
        finalDecBlock = decryptor.decryptFinal();
        fullDecrypted = combineArrays(decrypted, finalDecBlock);
        
        assertArrayEquals("Decrypted unaligned data should match original", UNALIGNED_DATA, fullDecrypted);
    }

    @Test
    public void testSm4Ofb() {
        // OFB mode doesn't need padding as it's a stream cipher
        SM4 encryptor = new SM4(SM4.SM4_OFB, TEST_KEY, TEST_IV, SM4.MODE_ENCRYPT, SM4.PADDING_NONE);
        SM4 decryptor = new SM4(SM4.SM4_OFB, TEST_KEY, TEST_IV, SM4.MODE_DECRYPT, SM4.PADDING_NONE);

        // Encrypt
        byte[] encrypted = encryptor.encryptUpdate(TEST_DATA, 0, TEST_DATA.length);
        byte[] finalEncBlock = encryptor.encryptFinal();
        byte[] fullEncrypted = combineArrays(encrypted, finalEncBlock);

        // Decrypt
        byte[] decrypted = decryptor.decryptUpdate(fullEncrypted, 0, fullEncrypted.length);
        byte[] finalDecBlock = decryptor.decryptFinal();
        byte[] fullDecrypted = combineArrays(decrypted, finalDecBlock);

        assertArrayEquals("Decrypted data should match original data", TEST_DATA, fullDecrypted);
        
        // Test with unaligned data
        encryptor = new SM4(SM4.SM4_OFB, TEST_KEY, TEST_IV, SM4.MODE_ENCRYPT, SM4.PADDING_NONE);
        decryptor = new SM4(SM4.SM4_OFB, TEST_KEY, TEST_IV, SM4.MODE_DECRYPT, SM4.PADDING_NONE);
        
        encrypted = encryptor.encryptUpdate(UNALIGNED_DATA, 0, UNALIGNED_DATA.length);
        finalEncBlock = encryptor.encryptFinal();
        fullEncrypted = combineArrays(encrypted, finalEncBlock);
        
        decrypted = decryptor.decryptUpdate(fullEncrypted, 0, fullEncrypted.length);
        finalDecBlock = decryptor.decryptFinal();
        fullDecrypted = combineArrays(decrypted, finalDecBlock);
        
        assertArrayEquals("Decrypted unaligned data should match original", UNALIGNED_DATA, fullDecrypted);
    }

    @Test
    public void testSm4Gcm() {
        // GCM mode is an authenticated encryption mode
        SM4 encryptor = new SM4(SM4.SM4_GCM, TEST_KEY, TEST_IV, SM4.MODE_ENCRYPT, SM4.PADDING_PKCS7);
        SM4 decryptor = new SM4(SM4.SM4_GCM, TEST_KEY, TEST_IV, SM4.MODE_DECRYPT, SM4.PADDING_PKCS7);

        // Encrypt
        byte[] encrypted = encryptor.encryptUpdate(TEST_DATA, 0, TEST_DATA.length);
        byte[] finalEncBlock = encryptor.encryptFinal();
        byte[] fullEncrypted = combineArrays(encrypted, finalEncBlock);

        // Decrypt
        byte[] decrypted = decryptor.decryptUpdate(fullEncrypted, 0, fullEncrypted.length);
        byte[] finalDecBlock = decryptor.decryptFinal();
        byte[] fullDecrypted = combineArrays(decrypted, finalDecBlock);

        assertArrayEquals("Decrypted data should match original data", TEST_DATA, fullDecrypted);
        
        // Test with unaligned data
        encryptor = new SM4(SM4.SM4_GCM, TEST_KEY, TEST_IV, SM4.MODE_ENCRYPT, SM4.PADDING_PKCS7);
        decryptor = new SM4(SM4.SM4_GCM, TEST_KEY, TEST_IV, SM4.MODE_DECRYPT, SM4.PADDING_PKCS7);
        
        encrypted = encryptor.encryptUpdate(UNALIGNED_DATA, 0, UNALIGNED_DATA.length);
        finalEncBlock = encryptor.encryptFinal();
        fullEncrypted = combineArrays(encrypted, finalEncBlock);
        
        decrypted = decryptor.decryptUpdate(fullEncrypted, 0, fullEncrypted.length);
        finalDecBlock = decryptor.decryptFinal();
        fullDecrypted = combineArrays(decrypted, finalDecBlock);
        
        assertArrayEquals("Decrypted unaligned data should match original", UNALIGNED_DATA, fullDecrypted);
    }

    @Test
    public void testSm4Xts() {
        // XTS mode is a stream cipher mode used for disk encryption
        // It requires a double-length key (32 bytes) and doesn't use padding
        SM4 encryptor = new SM4(SM4.SM4_XTS, TEST_KEY_XTS, TEST_IV, SM4.MODE_ENCRYPT, SM4.PADDING_NONE);
        SM4 decryptor = new SM4(SM4.SM4_XTS, TEST_KEY_XTS, TEST_IV, SM4.MODE_DECRYPT, SM4.PADDING_NONE);

        // Use TEST_DATA which is already block-aligned (32 bytes)
        // Encrypt - XTS mode doesn't use padding, so no need for encryptFinal
        byte[] encrypted = encryptor.encryptUpdate(TEST_DATA, 0, TEST_DATA.length);

        // Decrypt - XTS mode doesn't use padding, so no need for decryptFinal
        byte[] decrypted = decryptor.decryptUpdate(encrypted, 0, encrypted.length);

        assertArrayEquals("Decrypted data should match original data", TEST_DATA, decrypted);
        
        // XTS mode requires block-aligned data, so we'll pad UNALIGNED_DATA to block size manually
        int blockSize = 16; // SM4 block size is 16 bytes
        int paddingLength = blockSize - (UNALIGNED_DATA.length % blockSize);
        byte[] paddedData = Arrays.copyOf(UNALIGNED_DATA, UNALIGNED_DATA.length + paddingLength);
        
        encryptor = new SM4(SM4.SM4_XTS, TEST_KEY_XTS, TEST_IV, SM4.MODE_ENCRYPT, SM4.PADDING_NONE);
        decryptor = new SM4(SM4.SM4_XTS, TEST_KEY_XTS, TEST_IV, SM4.MODE_DECRYPT, SM4.PADDING_NONE);
        
        encrypted = encryptor.encryptUpdate(paddedData, 0, paddedData.length);
        decrypted = decryptor.decryptUpdate(encrypted, 0, encrypted.length);

        // Compare only the unpadded part
        byte[] unpadded = Arrays.copyOf(decrypted, UNALIGNED_DATA.length);
        assertArrayEquals("Decrypted unaligned data should match original", UNALIGNED_DATA, unpadded);
    }

    @Test
    public void testSm4CbcJce() throws Exception {
        // Register HITLS provider if not already registered
        if (Security.getProvider(HiTls4jProvider.PROVIDER_NAME) == null) {
            Security.addProvider(new HiTls4jProvider());
        }

        // Create key and IV specifications
        SecretKeySpec keySpec = new SecretKeySpec(TEST_KEY, "SM4");
        IvParameterSpec ivSpec = new IvParameterSpec(TEST_IV);

        // Initialize cipher for encryption
        Cipher encryptCipher = Cipher.getInstance("SM4/CBC/PKCS7PADDING", HiTls4jProvider.PROVIDER_NAME);
        encryptCipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);

        // Encrypt the test data
        byte[] encryptedData = encryptCipher.doFinal(UNALIGNED_DATA);

        // Initialize cipher for decryption
        Cipher decryptCipher = Cipher.getInstance("SM4/CBC/PKCS7PADDING", HiTls4jProvider.PROVIDER_NAME);
        decryptCipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);

        // Decrypt the data
        byte[] decryptedData = decryptCipher.doFinal(encryptedData);

        // Verify the decrypted data matches the original
        assertArrayEquals("Decrypted data should match original", UNALIGNED_DATA, decryptedData);
    }
    
    @Test
    public void testSm4CtrJce() {
        try {
            // Register HITLS provider if not already registered
            if (Security.getProvider(HiTls4jProvider.PROVIDER_NAME) == null) {
                Security.addProvider(new HiTls4jProvider());
            }

            // Create key and IV specifications
            SecretKeySpec keySpec = new SecretKeySpec(TEST_KEY, "SM4");
            IvParameterSpec ivSpec = new IvParameterSpec(TEST_IV);

            // Initialize cipher for encryption
            Cipher encryptCipher = Cipher.getInstance("SM4/CTR/NOPADDING", HiTls4jProvider.PROVIDER_NAME);
            encryptCipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);

            // Encrypt the test data
            byte[] encryptedData = encryptCipher.doFinal(TEST_DATA);

            // Initialize cipher for decryption
            Cipher decryptCipher = Cipher.getInstance("SM4/CTR/NOPADDING", HiTls4jProvider.PROVIDER_NAME);
            decryptCipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);

            // Decrypt the data
            byte[] decryptedData = decryptCipher.doFinal(encryptedData);

            // Verify the decrypted data matches the original
            assertArrayEquals("Decrypted data should match original", TEST_DATA, decryptedData);

            // Test with unaligned data
            encryptedData = encryptCipher.doFinal(UNALIGNED_DATA);
            decryptedData = decryptCipher.doFinal(encryptedData);
            assertArrayEquals("Decrypted unaligned data should match original", UNALIGNED_DATA, decryptedData);
        } catch (Exception e) {
            e.printStackTrace();
            fail("Exception occurred: " + e.getMessage());
        }
    }

    @Test
    public void testSm4CfbJce() {
        try {
            // Register HITLS provider if not already registered
            if (Security.getProvider(HiTls4jProvider.PROVIDER_NAME) == null) {
                Security.addProvider(new HiTls4jProvider());
            }

            // Create key and IV specifications
            SecretKeySpec keySpec = new SecretKeySpec(TEST_KEY, "SM4");
            IvParameterSpec ivSpec = new IvParameterSpec(TEST_IV);

            // Initialize cipher for encryption
            Cipher encryptCipher = Cipher.getInstance("SM4/CFB/NOPADDING", HiTls4jProvider.PROVIDER_NAME);
            encryptCipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);

            // Encrypt the test data
            byte[] encryptedData = encryptCipher.doFinal(TEST_DATA);

            // Initialize cipher for decryption
            Cipher decryptCipher = Cipher.getInstance("SM4/CFB/NOPADDING", HiTls4jProvider.PROVIDER_NAME);
            decryptCipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);

            // Decrypt the data
            byte[] decryptedData = decryptCipher.doFinal(encryptedData);

            // Verify the decrypted data matches the original
            assertArrayEquals("Decrypted data should match original", TEST_DATA, decryptedData);

            // Test with unaligned data
            encryptedData = encryptCipher.doFinal(UNALIGNED_DATA);
            decryptedData = decryptCipher.doFinal(encryptedData);
            assertArrayEquals("Decrypted unaligned data should match original", UNALIGNED_DATA, decryptedData);
        } catch (Exception e) {
            e.printStackTrace();
            fail("Exception occurred: " + e.getMessage());
        }
    }

    @Test
    public void testSm4OfbJce() {
        try {
            // Register HITLS provider if not already registered
            if (Security.getProvider(HiTls4jProvider.PROVIDER_NAME) == null) {
                Security.addProvider(new HiTls4jProvider());
            }

            // Create key and IV specifications
            SecretKeySpec keySpec = new SecretKeySpec(TEST_KEY, "SM4");
            IvParameterSpec ivSpec = new IvParameterSpec(TEST_IV);

            // Initialize cipher for encryption
            Cipher encryptCipher = Cipher.getInstance("SM4/OFB/NOPADDING", HiTls4jProvider.PROVIDER_NAME);
            encryptCipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);

            // Encrypt the test data
            byte[] encryptedData = encryptCipher.doFinal(TEST_DATA);

            // Initialize cipher for decryption
            Cipher decryptCipher = Cipher.getInstance("SM4/OFB/NOPADDING", HiTls4jProvider.PROVIDER_NAME);
            decryptCipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);

            // Decrypt the data
            byte[] decryptedData = decryptCipher.doFinal(encryptedData);

            // Verify the decrypted data matches the original
            assertArrayEquals("Decrypted data should match original", TEST_DATA, decryptedData);

            // Test with unaligned data
            encryptedData = encryptCipher.doFinal(UNALIGNED_DATA);
            decryptedData = decryptCipher.doFinal(encryptedData);
            assertArrayEquals("Decrypted unaligned data should match original", UNALIGNED_DATA, decryptedData);
        } catch (Exception e) {
            e.printStackTrace();
            fail("Exception occurred: " + e.getMessage());
        }
    }

    @Test
    public void testSm4GcmJce() {
        try {
            // Register HITLS provider if not already registered
            if (Security.getProvider(HiTls4jProvider.PROVIDER_NAME) == null) {
                Security.addProvider(new HiTls4jProvider());
            }

            // Create key and IV specifications
            SecretKeySpec keySpec = new SecretKeySpec(TEST_KEY, "SM4");
            IvParameterSpec ivSpec = new IvParameterSpec(TEST_IV);

            // Initialize cipher for encryption
            Cipher encryptCipher = Cipher.getInstance("SM4/GCM/NOPADDING", HiTls4jProvider.PROVIDER_NAME);
            encryptCipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);

            // Encrypt the test data
            byte[] encryptedData = encryptCipher.doFinal(TEST_DATA);

            // Initialize cipher for decryption
            Cipher decryptCipher = Cipher.getInstance("SM4/GCM/NOPADDING", HiTls4jProvider.PROVIDER_NAME);
            decryptCipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);

            // Decrypt the data
            byte[] decryptedData = decryptCipher.doFinal(encryptedData);

            // Verify the decrypted data matches the original
            assertArrayEquals("Decrypted data should match original", TEST_DATA, decryptedData);

            // Test with unaligned data
            encryptedData = encryptCipher.doFinal(UNALIGNED_DATA);
            decryptedData = decryptCipher.doFinal(encryptedData);
            assertArrayEquals("Decrypted unaligned data should match original", UNALIGNED_DATA, decryptedData);
        } catch (Exception e) {
            e.printStackTrace();
            fail("Exception occurred: " + e.getMessage());
        }
    }

    @Test
    public void testSm4XtsJce() {
        try {
            // Register HITLS provider if not already registered
            if (Security.getProvider(HiTls4jProvider.PROVIDER_NAME) == null) {
                Security.addProvider(new HiTls4jProvider());
            }

            // Create key and IV specifications
            SecretKeySpec keySpec = new SecretKeySpec(TEST_KEY_XTS, "SM4");
            IvParameterSpec ivSpec = new IvParameterSpec(TEST_IV);

            // Initialize cipher for encryption
            Cipher encryptCipher = Cipher.getInstance("SM4/XTS/NOPADDING", HiTls4jProvider.PROVIDER_NAME);
            encryptCipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);

            // For XTS mode, input length must be at least one block (16 bytes)
            byte[] testData = new byte[32];  // Using 2 blocks for testing
            System.arraycopy(TEST_DATA, 0, testData, 0, 32);

            // Encrypt the test data
            byte[] encryptedData = encryptCipher.doFinal(testData);

            // Initialize cipher for decryption
            Cipher decryptCipher = Cipher.getInstance("SM4/XTS/NOPADDING", HiTls4jProvider.PROVIDER_NAME);
            decryptCipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);

            // Decrypt the data
            byte[] decryptedData = decryptCipher.doFinal(encryptedData);

            // Verify the decrypted data matches the original
            assertArrayEquals("Decrypted data should match original", testData, decryptedData);
        } catch (Exception e) {
            e.printStackTrace();
            fail("Exception occurred: " + e.getMessage());
        }
    }

    @Test
    public void testSm4CbcPkcs5JceTest() throws Exception {
        // Register HITLS provider if not already registered
        if (Security.getProvider(HiTls4jProvider.PROVIDER_NAME) == null) {
            Security.addProvider(new HiTls4jProvider());
        }

        // Create key and IV specifications
        SecretKeySpec keySpec = new SecretKeySpec(TEST_KEY, "SM4");
        IvParameterSpec ivSpec = new IvParameterSpec(TEST_IV);

        // Initialize cipher for encryption
        Cipher encryptCipher = Cipher.getInstance("SM4/CBC/PKCS5PADDING", HiTls4jProvider.PROVIDER_NAME);
        encryptCipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);

        // Encrypt the test data
        byte[] encryptedData = encryptCipher.doFinal(UNALIGNED_DATA);

        // Initialize cipher for decryption
        Cipher decryptCipher = Cipher.getInstance("SM4/CBC/PKCS5PADDING", HiTls4jProvider.PROVIDER_NAME);
        decryptCipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);

        // Decrypt the data
        byte[] decryptedData = decryptCipher.doFinal(encryptedData);

        // Verify the decrypted data matches the original
        assertArrayEquals("Decrypted data should match original", UNALIGNED_DATA, decryptedData);
    }

    @Test
    public void testSm4CbcIso7816JceTest() throws Exception {
        // Register HITLS provider if not already registered
        if (Security.getProvider(HiTls4jProvider.PROVIDER_NAME) == null) {
            Security.addProvider(new HiTls4jProvider());
        }

        // Create key and IV specifications
        SecretKeySpec keySpec = new SecretKeySpec(TEST_KEY, "SM4");
        IvParameterSpec ivSpec = new IvParameterSpec(TEST_IV);

        // Initialize cipher for encryption
        Cipher encryptCipher = Cipher.getInstance("SM4/CBC/ISO7816PADDING", HiTls4jProvider.PROVIDER_NAME);
        encryptCipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);

        // Encrypt the test data
        byte[] encryptedData = encryptCipher.doFinal(UNALIGNED_DATA);

        // Initialize cipher for decryption
        Cipher decryptCipher = Cipher.getInstance("SM4/CBC/ISO7816PADDING", HiTls4jProvider.PROVIDER_NAME);
        decryptCipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);

        // Decrypt the data
        byte[] decryptedData = decryptCipher.doFinal(encryptedData);

        // Verify the decrypted data matches the original
        assertArrayEquals("Decrypted data should match original", UNALIGNED_DATA, decryptedData);
    }

    @Test
    public void testSm4CbcZerosJceTest() throws Exception {
        // Register HITLS provider if not already registered
        if (Security.getProvider(HiTls4jProvider.PROVIDER_NAME) == null) {
            Security.addProvider(new HiTls4jProvider());
        }

        // Create key and IV specifications
        SecretKeySpec keySpec = new SecretKeySpec(TEST_KEY, "SM4");
        IvParameterSpec ivSpec = new IvParameterSpec(TEST_IV);

        // Initialize cipher for encryption
        Cipher encryptCipher = Cipher.getInstance("SM4/CBC/ZEROSPADDING", HiTls4jProvider.PROVIDER_NAME);
        encryptCipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);

        // Encrypt the test data
        byte[] encryptedData = encryptCipher.doFinal(UNALIGNED_DATA);

        // Initialize cipher for decryption
        Cipher decryptCipher = Cipher.getInstance("SM4/CBC/ZEROSPADDING", HiTls4jProvider.PROVIDER_NAME);
        decryptCipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);

        // Decrypt the data
        byte[] decryptedData = decryptCipher.doFinal(encryptedData);

        // For zeros padding, we need to trim trailing zeros
        int actualLength = UNALIGNED_DATA.length;
        byte[] trimmedDecrypted = Arrays.copyOf(decryptedData, actualLength);

        // Verify the decrypted data matches the original
        assertArrayEquals("Decrypted data should match original", UNALIGNED_DATA, trimmedDecrypted);
    }

    @Test
    public void testSm4EcbPkcs7JceTest() throws Exception {
        // Register HITLS provider if not already registered
        if (Security.getProvider(HiTls4jProvider.PROVIDER_NAME) == null) {
            Security.addProvider(new HiTls4jProvider());
        }

        // Create key specification
        SecretKeySpec keySpec = new SecretKeySpec(TEST_KEY, "SM4");

        // Initialize cipher for encryption
        Cipher encryptCipher = Cipher.getInstance("SM4/ECB/PKCS7PADDING", HiTls4jProvider.PROVIDER_NAME);
        encryptCipher.init(Cipher.ENCRYPT_MODE, keySpec);

        // Encrypt the test data
        byte[] encryptedData = encryptCipher.doFinal(UNALIGNED_DATA);

        // Initialize cipher for decryption
        Cipher decryptCipher = Cipher.getInstance("SM4/ECB/PKCS7PADDING", HiTls4jProvider.PROVIDER_NAME);
        decryptCipher.init(Cipher.DECRYPT_MODE, keySpec);

        // Decrypt the data
        byte[] decryptedData = decryptCipher.doFinal(encryptedData);

        // Verify the decrypted data matches the original
        assertArrayEquals("Decrypted data should match original", UNALIGNED_DATA, decryptedData);
    }

    @Test
    public void testSm4EcbPkcs5JceTest() throws Exception {
        // Register HITLS provider if not already registered
        if (Security.getProvider(HiTls4jProvider.PROVIDER_NAME) == null) {
            Security.addProvider(new HiTls4jProvider());
        }

        // Create key specification
        SecretKeySpec keySpec = new SecretKeySpec(TEST_KEY, "SM4");

        // Initialize cipher for encryption
        Cipher encryptCipher = Cipher.getInstance("SM4/ECB/PKCS5PADDING", HiTls4jProvider.PROVIDER_NAME);
        encryptCipher.init(Cipher.ENCRYPT_MODE, keySpec);

        // Encrypt the test data
        byte[] encryptedData = encryptCipher.doFinal(UNALIGNED_DATA);

        // Initialize cipher for decryption
        Cipher decryptCipher = Cipher.getInstance("SM4/ECB/PKCS5PADDING", HiTls4jProvider.PROVIDER_NAME);
        decryptCipher.init(Cipher.DECRYPT_MODE, keySpec);

        // Decrypt the data
        byte[] decryptedData = decryptCipher.doFinal(encryptedData);

        // Verify the decrypted data matches the original
        assertArrayEquals("Decrypted data should match original", UNALIGNED_DATA, decryptedData);
    }

    @Test
    public void testSm4EcbIso7816JceTest() throws Exception {
        // Register HITLS provider if not already registered
        if (Security.getProvider(HiTls4jProvider.PROVIDER_NAME) == null) {
            Security.addProvider(new HiTls4jProvider());
        }

        // Create key specification
        SecretKeySpec keySpec = new SecretKeySpec(TEST_KEY, "SM4");

        // Initialize cipher for encryption
        Cipher encryptCipher = Cipher.getInstance("SM4/ECB/ISO7816PADDING", HiTls4jProvider.PROVIDER_NAME);
        encryptCipher.init(Cipher.ENCRYPT_MODE, keySpec);

        // Encrypt the test data
        byte[] encryptedData = encryptCipher.doFinal(UNALIGNED_DATA);

        // Initialize cipher for decryption
        Cipher decryptCipher = Cipher.getInstance("SM4/ECB/ISO7816PADDING", HiTls4jProvider.PROVIDER_NAME);
        decryptCipher.init(Cipher.DECRYPT_MODE, keySpec);

        // Decrypt the data
        byte[] decryptedData = decryptCipher.doFinal(encryptedData);

        // Verify the decrypted data matches the original
        assertArrayEquals("Decrypted data should match original", UNALIGNED_DATA, decryptedData);
    }

    @Test
    public void testSm4EcbZerosJceTest() throws Exception {
        // Register HITLS provider if not already registered
        if (Security.getProvider(HiTls4jProvider.PROVIDER_NAME) == null) {
            Security.addProvider(new HiTls4jProvider());
        }

        // Create key specification
        SecretKeySpec keySpec = new SecretKeySpec(TEST_KEY, "SM4");

        // Initialize cipher for encryption
        Cipher encryptCipher = Cipher.getInstance("SM4/ECB/ZEROSPADDING", HiTls4jProvider.PROVIDER_NAME);
        encryptCipher.init(Cipher.ENCRYPT_MODE, keySpec);

        // Encrypt the test data
        byte[] encryptedData = encryptCipher.doFinal(UNALIGNED_DATA);

        // Initialize cipher for decryption
        Cipher decryptCipher = Cipher.getInstance("SM4/ECB/ZEROSPADDING", HiTls4jProvider.PROVIDER_NAME);
        decryptCipher.init(Cipher.DECRYPT_MODE, keySpec);

        // Decrypt the data
        byte[] decryptedData = decryptCipher.doFinal(encryptedData);

        // For zeros padding, we need to trim trailing zeros
        int actualLength = UNALIGNED_DATA.length;
        byte[] trimmedDecrypted = Arrays.copyOf(decryptedData, actualLength);

        // Verify the decrypted data matches the original
        assertArrayEquals("Decrypted data should match original", UNALIGNED_DATA, trimmedDecrypted);
    }

    private byte[] combineArrays(byte[] first, byte[] second) {
        if (first == null) return second;
        if (second == null) return first;
        byte[] result = Arrays.copyOf(first, first.length + second.length);
        System.arraycopy(second, 0, result, first.length, second.length);
        return result;
    }
}